package com.ycg.biquge.uitls;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ycg.biquge.pojo.Author;
import com.ycg.biquge.pojo.Chapter;
import com.ycg.biquge.pojo.Novel;
import com.ycg.biquge.service.AuthorService;
import com.ycg.biquge.service.ChapterService;
import com.ycg.biquge.service.NovelService;
import jakarta.annotation.Resource;
import lombok.Data;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author YCG
 * @date 2024-04-16 20:18
 * @description 爬虫
 */
@Data
@Scope(value = "prototype")
@Component("crawler")
public class Crawler {

    @Resource
    private AuthorService authorService;
    @Resource
    private ChapterService chapterService;
    @Resource
    private NovelService novelService;

    /**
     * 网址:  https://www.biqg.cc
     */
    private String url = "";

    /**
     * 小说路径: /book/1000
     */
    private String bookId = "";

    /**
     * 屏蔽章节内容中的词
     */
    private List<String> ignore = new ArrayList<>();

    /**
     * 保存小说信息
     */
    private final Map<String, String> novelInfo = new HashMap<>(8);

    /**
     * 总章节数
     */
    private Integer total = 0;

    /**
     * 根据url爬取资源(笔趣阁)
     */
    public void crawl() {
        HashMap<String, String> map = new HashMap<>();
        map.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36");
        Document doc = null;
        int errorCount = 0;
        while (errorCount < 10) {
            try {
                doc = Jsoup.connect(url + "/book/" + bookId).timeout(60000).headers(map).get();
                break;
            } catch (Exception e) {
                System.err.println("小说重试次数:" + ++errorCount);
                e.printStackTrace();
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
        if (doc == null) {
            // 说明报错了, 直接返回,查询下一篇小说
            return;
        }
        // 保存作者和小说信息
        Integer novelId = setInfo(doc);
        // 获取doc中的每章节url
        ArrayList<String> pathUrls = getUrls(doc);

        // 章节号, 不一定和章节一一对应
        int count = 1;
        int chapterErrorCount = 0;
        for (String url : pathUrls) {
            Document document = null;
            if (chapterErrorCount > 5) continue;
            if (count >= 200) return;

            int errorCount2 = 0;
            while (errorCount2 < 5) {
                try {
                    document = Jsoup.connect(url).timeout(60000).get();
                    break;
                } catch (Exception e) {
                    chapterErrorCount++;
                    e.printStackTrace();
                    System.err.println(url + "章节重试次数: " + ++errorCount2);
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
            if (document == null) {
                // 说明报错了, 直接返回,查询下一章节
                continue;
            }
            Element chapterContent = document.getElementById("chaptercontent");
            // 文章标题
            String title = getTitle(document);

            List<Node> nodes = chapterContent.childNodes();
            // 获取文章内容
            StringBuilder content = getContent(nodes);
            write2Db(novelId, title, content, count++);
        }

    }



    /**
     * 获取文章内容
     *
     * @param nodes
     * @return
     */
    private StringBuilder getContent(List<Node> nodes) {
        // 每篇文章最长3000字
        StringBuilder content = new StringBuilder(3000);
        for (int i = 0; i < nodes.size() - 1; i++) {
            Node node = nodes.get(i);
            if (node.nodeName().equals("br") || ignore.contains(node.toString()) || node.toString().contains("请收藏本站") || node.toString().contains("83中文网最新地址")) {
                continue;
            }
            if (node.toString().contains("\\x")) break;
            if (content.length() > 3000) break;
            content.append(node).append("\n");

        }
        return content;
    }

    /**
     * 获取文章标题
     *
     * @param document
     * @return
     */
    private String getTitle(Document document) {
        String title = "";
        for (Element content : document.getElementsByClass("content")) {
            title = content.getElementsByTag("h1").first().text();
            break;
        }
        return title;
    }

    /**
     * 获取doc中的每个章节url
     *
     * @param doc
     * @return
     */
    private ArrayList<String> getUrls(Document doc) {
        ArrayList<String> pathUrl = new ArrayList<>();
        Elements listmain = doc.getElementsByClass("listmain");
        for (Element dd : listmain.get(0).getElementsByTag("a")) {
            String oneUrl = url + dd.attr("href");
            if (oneUrl.contains(".html")) {
                pathUrl.add(oneUrl);
            }
        }
        total = pathUrl.size();
        return pathUrl;
    }

    /**
     * 写入小说和作者信息到数据库
     *
     * @param doc 小说章节列表页
     * @return 小说id
     */
    private Integer setInfo(Document doc) {
        for (Element meta : doc.getElementsByTag("meta")) {
            getInfo(meta);
        }
        // 保存小说和作者信息到数据
        Novel novel = save();
        return novel.getId();
    }

    /**
     * 根据meta获取小说和作者信息
     *
     * @param meta
     */
    private void getInfo(Element meta) {
        boolean hit = false;
        String key = "";
        for (Attribute attribute : meta.attributes()) {
            if (hit) {
                novelInfo.put(key, attribute.getValue());
            }
            if (Objects.equals(attribute.getKey(), "property")) {
                hit = true;
                // 根据meta获取小说和作者信息
                switch (attribute.getValue()) {
                    case "og:novel:book_name" -> key = "bookName";
                    case "og:image" -> key = "picture";
                    case "og:description" -> key = "description";
                    case "og:novel:category" -> key = "category";
                    case "og:novel:author" -> key = "author";
                    case "og:novel:status" -> key = "status";
                    case "og:novel:update_time" -> key = "updateTime";
                    case "og:novel:latest_chapter_name" -> key = "updateChapterName";
                    default -> hit = false;
                }
            }
        }
    }

    /**
     * 保存小说和作者信息
     *
     * @return
     */
    private Novel save() {
        Novel novel = new Novel();
        novel.setAuthor(novelInfo.get("author"));
        novel.setStatus(novelInfo.get("status"));
        novel.setName(novelInfo.get("bookName"));
        novel.setNewChapter(novelInfo.get(("updateChapterName")));
        novel.setType(novelInfo.get("category"));
        novel.setSimple(novelInfo.get("description"));
        novel.setUpdateTime(LocalDateTime.parse(novelInfo.get("updateTime"), DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss")));
        novel.setPicture(novelInfo.get("picture"));

        System.out.println("正在爬取小说:" + novel + "===========================");
        novelService.save(novel);

        Author authorInfo = new Author();
        authorInfo.setName(novelInfo.get("author"));
        authorInfo.setNovels(String.valueOf(novel.getId()));

        LambdaQueryWrapper<Author> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Author::getName,novelInfo.get("author"));
        Author one = authorService.getOne(wrapper);
        // 如果查询到, 则说明已经存在了该作者, 只需将小说id放入末尾即可 使用,分割
        if (one != null) {
            one.setNovels(one.getNovels()+","+novel.getId());
            authorService.updateById(one);
        } else {
            // 不存在, 直接保存
            authorService.save(authorInfo);
        }
        return novel;
    }

    /**
     * 写入数据库
     */
    private void write2Db(Integer novelId, String title, StringBuilder content, int count) {
        System.out.println("小说id: " + novelId + "===章节名称: " + title);
        Chapter chapter = new Chapter();
        chapter.setNum(count);
        chapter.setNovelId(novelId);
        chapter.setTitle(title);
        String zip = zip(content.toString());
        chapter.setContent(zip);
        try {
            chapterService.save(chapter);
        } catch (Exception ex) {
            System.out.println("章节保存处理异常..." + ex.getMessage());
        }
    }

    private String zip(String s) {
        if (StrUtil.isBlank(s)) return "章节错误";
        byte[] gzip = ZipUtil.gzip(s.getBytes(StandardCharsets.UTF_8));
        return Base64.encode(gzip);
    }

}
